/*
 * @(#)XfdmActiveRules.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfdm.model.algorithm;

import xfuzzy.lang.*;
import xfuzzy.xfdm.model.XfdmAlgorithm;
import xfuzzy.xfdm.model.XfdmConfig;
import xfuzzy.xfds.XfdsDataSet;

/**
 * Clase que desarrolla el algoritmo FuzzConRI
 * 
 * @author Francisco Jos� Moreno Velo
 *
 */
public class XfdmFuzzConRI extends XfdmAlgorithm {

	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * N�mero de antecedentes a mantener en la estrella
	 */
	private int star_size;
	
	/**
	 * Umbral de significancia estad�stica
	 */
	private double likelihood;
	
	/**
	 * Corte-alpha para discriminar instancias positivas
	 */
	private double cut;
	
	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor
	 */
	public XfdmFuzzConRI(int s, double c, double l){
		this.star_size = s;
		this.cut = c;
		this.likelihood = l;
	}
	
	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene el nombre del algoritmo
	 */
	public String toString() {
		return "FuzzyConRI";
	}

	/**
	 * Representaci�n en el fichero de configuraci�n
	 */
	public String toXML() {
		String eol = System.getProperty("line.separator", "\n");
		String code = "\t\t<algorithm name=\"FuzzyConRI\" >"+eol;
		code += "\t\t\t<param name=\"star_size\" value=\""+star_size+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"alpha_cut\" value=\""+cut+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"likelihood\" value=\""+likelihood+"\" ></param>"+eol;
		code +=	"\t\t</algorithm>";
		return code;
	}

	/**
	 * Obtiene el tipo de base de conocimiento que genera el algoritmo
	 */
	public int getKnowledgeBaseKind() {
		return KnowledgeBase.RULE_SET;
	}

	/**
	 * Obtiene un duplicado del objeto
	 */
	public Object clone() {
		return new XfdmFuzzConRI(star_size,cut,likelihood);
	}

	/**
	 * Indica si el algoritmo necesita que las variables de entrada tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de entrada.
	 */
	public boolean needInputMemFuncs() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo necesita que las variables de salida tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de salida.
	 */
	public boolean needOutputMemFuncs() {
		return true;
	}

	/**
	 * Indica si el algoritmo necesita que la estructura del sistema est� creada.
	 * Este requisito es propio de los algoritmo que contienen alguna etapa de
	 * optimizaci�n param�trica.
	 * @return
	 */
	public boolean needSystemStructure() {
		return false;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de clasificaci�n
	 * @return
	 */
	public boolean supportsClassification() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de regresi�n
	 * @return
	 */
	public boolean supportsRegression() {
		return false;
	}

	/**
	 * Asigna el valor al umbral de significancia estad�stica
	 * @param l
	 */
	public void setLikelihood(double l) {
		this.likelihood = l;
	}
	
	/**
	 * Obtiene el valor del umbral de significancia estad�stica
	 * @return
	 */
	public double getLikelihood() {
		return this.likelihood;
	}

	/**
	 * Asigna el valor del tama�o de la estrella
	 * @param s
	 */
	public void setStarSize(int s) {
		this.star_size = s;
	}
	
	/**
	 * Obtiene el valor del tama�o de la estrella
	 * @return
	 */
	public int getStarSize() {
		return this.star_size;
	}
	
	/**
	 * Asigna el valor del corte-alpha
	 * @param c
	 */
	public void setCut(double c) {
		this.cut = c;
	}
	
	/**
	 * Obtiene el valor del corte-alpha
	 * @return
	 */
	public double getCut() {
		return this.cut;
	}

	/**
	 * Genera el contenido de la base de conocimiento a partir de los datos
	 */
	public void extractKnowlegde(KnowledgeBase base, XfdmConfig config) {
		XfdsDataSet dataset = config.getDataSet();
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		Type[] itype = new Type[ivar.length];
		for(int i=0; i<ivar.length; i++) itype[i] = ivar[i].getType();
		XfdmFuzzyDataSet fzdataset = new XfdmFuzzyDataSet(dataset,itype,ovar[0].getType());
		computeFuzzyConRI(base, fzdataset);
	}
	
	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Ejecuta el bucle externo del algoritmo FuzzyConRI y genera las reglas
	 */
	private void computeFuzzyConRI(KnowledgeBase base, XfdmFuzzyDataSet fzdataset) {
		Variable ovar = base.getOutputs()[0];
		LinguisticLabel[] concept = ovar.getType().getAllMembershipFunctions();
		for(int i=0; i<concept.length; i++) {
			XfdmFuzzyDataSet P = fzdataset.selectPositiveInstances(i,cut);
			XfdmFuzzyDataSet N = fzdataset.selectNegativeInstances(i,cut);
			while(P.certainty.length > 0) {
				Relation rel = findBestAntecedent(base,P,N);
				if(rel == null) break;
				Rule rule = new Rule(rel);
				Conclusion conc = new Conclusion(ovar,concept[i],base);
				rule.add(conc);
				((RuleSet) base).addRule(rule);
			}
		}
	}
	
	/**
	 * Obtiene el mejor antecedente para los conjuntos de datos positivos (P) y 
	 * negativos (N) indicados.
	 * @param P
	 * @param N
	 * @return
	 */
	private Relation findBestAntecedent(KnowledgeBase base, XfdmFuzzyDataSet P, XfdmFuzzyDataSet N) {
		Variable[] ivar = base.getInputs();
		
		// Calcula el n�mero de MFs de cada entrada
		int[] numMFs = new int[ivar.length];
		int numSel = 0;
		for(int a=0; a<ivar.length; a++) { 
			int mfs = ivar[a].getType().getAllMembershipFunctions().length;
			numMFs[a] = mfs;
			numSel += mfs;
		}

		// Crea la lista de selectores
		// selector[i][0] se refiere a la variable de entrada
		// selector[i][1] se refiere a la MF de la variable
		int[][] selector = new int[numSel][2];
		for(int i=0,a=0, mf=0; i<numSel; i++) {
			selector[i][0] = a;
			selector[i][1] = mf;
			mf++;
			if(mf==numMFs[a]) { a++; mf = 0; }
		}
		
		// Un complejo es una lista de selectores int[][2]
		// Star es una lista de complejos
		
		int[][][] star = new int[1][0][2];
		int[][] best_complex = star[0];
		double best_quality = getQuality(base,star[0],P,N);
		
		while(star.length>0) {
			int[][][] newstar = new int[0][][];
			double[] quality = new double[0];
			
			for(int i=0; i<star.length; i++) { // para cada complejo de la estrella
				for(int s=0; s<selector.length; s++) { // para cada selector
					int[][] complex = addSelector(star[i],selector[s]);
					if(complex == null) continue;
					if(!isSignificant(base,complex,P)) continue;
					double complex_quality = getQuality(base,complex,P,N);
					if(complex_quality>best_quality) {
						best_quality = complex_quality;
						best_complex = complex;
					}
					
					newstar = appendComplex(newstar,quality,complex,complex_quality);
					quality = appendQuality(quality,complex_quality);
				}
			}
			
			star = newstar;
		}
		
		P.pruneInstances(best_complex, cut, base.getOperatorset().and);
		
		LinguisticLabel[][] imf = new LinguisticLabel[ivar.length][];
		for(int i=0; i<ivar.length; i++) imf[i] = ivar[i].getType().getAllMembershipFunctions();
		int var0 = best_complex[0][0];
		int mf0 = best_complex[0][1];
		Relation rel = Relation.create(Relation.IS, null, null, ivar[ var0 ], imf[var0][mf0], base);
		for(int i=1; i<best_complex.length; i++) {
			int var = best_complex[i][0];   // variable del selector i-esimo
			int mf = best_complex[i][1];    // etiqueta del selector i-esimo
			Relation right = Relation.create(Relation.IS, null, null, ivar[ var ], imf[var][mf], base);
			rel = Relation.create(Relation.AND, rel, right, null, null, base);
		}
		return rel;
	}
	
	/**
	 * A�ade un selector a un complejo. Si se repite el selector devuelve null.
	 * @param complex
	 * @param selector
	 * @return
	 */
	private int[][] addSelector(int[][] complex, int[] selector) {
		for(int i=0; i<complex.length; i++) {
			if(complex[i][0] == selector[0] && complex[i][1] == selector[1]) return null;
		}
		
		int[][] newcomplex = new int[complex.length+1][];
		for(int i=0; i<complex.length; i++) newcomplex[i] = complex[i];
		newcomplex[complex.length] = selector;
		return newcomplex;
	}
	
	/**
	 * A�ade un complejo a la estrella. Si se llega al tama�o m�ximo se elimina el peor de los complejos.
	 * @param star
	 * @param quality
	 * @param complex
	 * @param complex_quality
	 * @return
	 */
	private int[][][] appendComplex(int[][][]star, double[] quality, int[][]complex, double complex_quality){
		if(star.length < star_size) {
			int[][][] append = new int[ star.length+1 ][][];
			for(int i=0; i<star.length; i++) append[i]=star[i];
			append[star.length] = complex;
			return append;
		} else {
			double worst = quality[0];
			int worst_index = 0;
			for(int i=1; i<quality.length; i++) if(quality[i]<worst) { worst=quality[i]; worst_index = i; }
			if(worst<complex_quality) { star[worst_index] = complex; }
			return star;
		}	
	}

	/**
	 * A�ade una evaluaci�n de una complejo a la lista de evaluaciones. Si se llega al tama�o se
	 * elimina el valor m�s bajo 
	 * @param quality
	 * @param complex_quality
	 * @return
	 */
	private double[] appendQuality(double[] quality, double complex_quality){
		if(quality.length < star_size) {
			double[] append = new double[ quality.length+1 ];
			for(int i=0; i<quality.length; i++) append[i]=quality[i];
			append[quality.length] = complex_quality;
			return append;
		} else {
			double worst = quality[0];
			int worst_index = 0;
			for(int i=1; i<quality.length; i++) if(quality[i]<worst) { worst=quality[i]; worst_index = i; }
			if(worst<complex_quality) { quality[worst_index] = complex_quality; }
			return quality;
		}	
	}

	/**
	 * Estudia si el complejo es estad�sticamente significativo. Para ello estudia
	 * el n�mero de instancias positivas que cubre y verifica que sea mayor o igual
	 * que el umbral de significancia.
	 * @param complex Complejo a estudiar
	 * @param P Conjunto de ejemplos positivos
	 * @return
	 */
	private boolean isSignificant(KnowledgeBase base, int[][] complex, XfdmFuzzyDataSet P ) {
		int instances = P.certainty.length;
		if(instances == 0) return false;
		int cover = 0;
		for(int i=0; i<instances; i++) if(cover(base,complex,P,i)) cover++;
		if( (1.0*cover)/instances >= likelihood ) return true;	
		return false;
	}

	/**
	 * Verifica si un complejo cubre o no a una instancia de un conjunto de datos
	 * @param complex
	 * @param dataset
	 * @param index
	 * @return
	 */
	private boolean cover(KnowledgeBase base,int[][] complex, XfdmFuzzyDataSet dataset, int index) {
		double activation = activation(base,complex,dataset,index);
		return (activation>cut);
	}
	
	/**
	 * Calcula el grado de covertura de un complejo sobre una instancia
	 * @param complex
	 * @param dataset
	 * @param index
	 * @return
	 */
	private double activation(KnowledgeBase base, int[][] complex, XfdmFuzzyDataSet dataset, int index) {
		double activation = dataset.certainty[index];
		double input[][] = dataset.input[index];
		Binary and = base.getOperatorset().and;
		for(int s=0; s<complex.length; s++) {
			activation = and.compute(activation, input[complex[s][0]][complex[s][1]]);
		}
		return activation;
	}
	
	/**
	 * Calcula la medida de precision difusa del complejo (fuzzy accuracy)
	 * @param complex
	 * @param P
	 * @param N
	 * @return
	 */
	private double getQuality(KnowledgeBase base, int[][] complex, XfdmFuzzyDataSet P, XfdmFuzzyDataSet N) {
		double accuracy = 0;
		for(int i=0; i<P.certainty.length; i++) accuracy += activation(base,complex,P,i);
		for(int i=0; i<N.certainty.length; i++) accuracy -= activation(base,complex,N,i);
		return accuracy;
	}
	
}
